package com.autumn.juc.jmm;

/**
 * -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp（后台打印出JVM指令，底层为汇编指令）
 * 若使用上方参数配置，还需下载文件：hsdis-amd64.dll
 * 定位搜索使用putfield flag（即refresh方法里的flag=false）:0x00000000039bca87: lock addl $0x0,(%rsp)
 */
public class VisibilityTest {
    private boolean flag = true;
    private int count = 0;

    public void refresh() {
        // threadB对flag的写操作会 happens-before threadA对flag的读操作
        flag = false;
        System.out.println(Thread.currentThread().getName() + "修改flag:" + flag);
    }

    public void load() {
        System.out.println(Thread.currentThread().getName() + "开始执行……");
        while (flag) {
            // TODO 业务逻辑
            count++;

            // 没有跳出循环，出现了可见性问题
            // 解决方案一：flag加上volatile修饰，底层由C++调用，调用了JVM内存屏障storeLoad，
            // 				x86的Linux里调用内存屏障时使用了汇编层面的lock前缀指令（lock; addl $0,0(%%rsp)）
            //              但是lock前缀指令并非内存屏障指令，但有内存屏障的效果，可以使其他缓存副本立即失效，当使用时便会从主内存加载最新的变量

            // 解决方案二：使用JVM层的内存屏障:UnsafeFactory.getUnsafe().storeFence();storeFence方法的底层仍是调用汇编层面的lock前缀指令

            // 解决方案三：Thread.yield();释放时间片，即上下文切换，之前的变量会进行保存上下文到寄存器，本地缓存便会失效，
            // 				当此线程继续运行时，根据程序计数器记录的位置继续往下执行，此时便会加载上下文，从主内存加载到最新的变量，跳出循环

            // 解决方案四：System.out.println(count);println内部使用了synchronized，本质上还是使用了内存屏障
            //				包括LockSuppotr.unpark(Thread.currentThread());
            //				shortWait()方法可跳出循环，若直接将方法内容放到while则无法跳出循环，do while在while中不可见，while在while中可见，
            //						因此为了避免while的业务逻辑中存在do while，可选择直接在共享变量上用volatile修饰
            //shortWait(1000000);// 1ms等待可以使得缓存被淘汰，更新到主内存最新的变量，跳出循环
            //shortWait(100);// 若等待太短，缓存不会被淘汰，无法跳出循环
            //				Thread.sleep(1);

            // 解决方案五：count换成Integer，底层拆箱时new了一个Integer对象，最终传入value，value由final修饰

        }
        System.out.println(Thread.currentThread().getName() + "跳出循环：count=" + count);
    }

    public static void main(String[] args) throws InterruptedException {
        VisibilityTest test = new VisibilityTest();

        // 线程threadA模拟数据加载场景
        Thread threadA = new Thread(() -> test.load(), "threadA");
        threadA.start();

        // 让threadA执行一会儿
        Thread.sleep(1000);
        // 线程threadB通过flag控制threadA的执行时间
        Thread threadB = new Thread(() -> test.refresh(), "threadB");
        threadB.start();
    }

    public static void shortWait(long interval) {
        long start = System.nanoTime();
        long end;
        do {
            end = System.nanoTime();
        } while (start + interval >= end);
    }
}
